import tkinter as tk
from tkinter import ttk, colorchooser
from tkinter.filedialog import asksaveasfilename, askopenfilename
import math
from collections import deque
import json
from graphics.Shape import Circle, Rectangle, Ellipse, Line, Brush, Polygon, PolygonGenerator
from graphics.TextBox import TextBox
from graphics.Flowchart import Arrow, StartOrEnd, Decision, OutputOrInput, Process, FlowLine


class GraphicsEditor:
    def __init__(self, root):
        self.now_drawing_polygon_type = None
        self.last_y = None
        self.last_x = None
        self.selected_shapes = None
        import warnings
        warnings.filterwarnings("ignore", category=UserWarning, module="PIL")
        self.root = root
        self.root.title("矢量图形编辑器 v1.0")
        self.current_shape = None
        self.selected_shape = None
        self.shapes = []

        # 标志当前流线输入的是start还是end
        self.flow_line_pos = False
        self.temp_start = None  #   用于暂时存储起始点

        # 表示文本输入完毕
        self.enter_TextBox = False

        # 当前画笔对象
        self.current_brush = None

        # 封闭图形填充
        self.flood_fill_queue = deque()  # 初始化泛洪填充队列
        self.canvas_image = None  # 初始化画布图像缓存
        self.current_fill = "#FFFFFF"  # 默认填充色

        style = ttk.Style()
        style.configure("Green.TButton", background="#00FF00", foreground="black")
        style.configure("Red.TButton", background="#FF0000", foreground="white")

        # 填充相关属性
        self.auto_fill = False
        self.fill_btn = None  # 新增按钮引用

        # 橡皮相关属性
        self.last_erase_point = None  # 新增类属性

        # 新增画笔相关属性
        self.point_counter = 0  # 坐标点计数器
        self.brush_points = []

        # 绘图状态控制
        self.drawing = False
        self.start_x = 0
        self.start_y = 0
        self.current_tool = "circle"  # 默认工具

        # 拖拽状态控制
        self.dragging_handle = None  # 当前拖拽的控制点类型（'scale'/'rotate'）
        self.last_pos = (0, 0)  # 记录上次鼠标位置

        self.setup_ui()

    def finalize_move(self, event):
        """
        用于选择功能的鼠标放开事件,放开之后什么也不做
        :param event: 空
        :return: 空
        """
        pass

    def handle_enter_key(self, event):
        """
        文本框用于在输入完之后使用回车来表示输入完成,立即重绘
        :param event: event
        :return:
        """
        # print(f"self.enter_TextBox:{self.enter_TextBox}")
        if self.enter_TextBox:
            self.enter_TextBox = False
            self.redraw()
        # self.redraw()
        return "break"  # 阻止事件传播

    def setup_ui(self):
        """创建界面组件"""
        # 主布局框架
        self.main_frame = ttk.Frame(self.root)
        self.main_frame.pack(fill=tk.BOTH, expand=True)
        menubar = tk.Menu(self.root)

        # 文件菜单
        file_menu = tk.Menu(menubar, tearoff=0)
        file_menu.add_command(label="打开", command=self.open_project)
        file_menu.add_command(label="保存", command=self.save_project)
        menubar.add_cascade(label="文件", menu=file_menu)

        self.root.config(menu=menubar)



        # 工具栏（左侧）
        self.tool_panel = ttk.LabelFrame(self.main_frame, text="工具")
        self.tool_panel.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)

        # 工具按钮组
        tools = [
            ("画笔", "brush"),
            ("文本框", "text"),
            ("直线", "line"),
            ("箭头", "arrow"),
            ("圆形", "circle"),
            ("椭圆", "ellipse"),
            ("矩形", "rectangle"),
            ("三角形", "triangle"),
            ("五角星", "star"),
            ("五边形", "pentagon"),
            ("正六边形","regular_hexagon"),
            ("菱形", "diamond"),
            ("平行四边形", "parallelogram"),
            ("六边形", "line_hexagon"),
            ("上切矩形", "upper_cut_hexagon"),
            ("下切矩形", "lower_cut_hexagon"),
            ("跑道形","generate_track"),
            ("选择", "select"),
            ("清屏", "clear"),
            ("图形橡皮擦", "erase"),
            ("StartOrEnd", "StartOrEnd"),
            ("Decision","Decision"),
            ("OutputOrInput", "OutputOrInput"),
            ("Process","Process"),
            ("FlowLine", "FlowLine")
        ]
        for text, cmd in tools:
            btn = ttk.Button(self.tool_panel, text=text,
                             command=lambda c=cmd: self.set_tool(c))
            btn.pack(pady=2, fill=tk.X)

        # 属性面板（右侧）
        self.prop_panel = ttk.LabelFrame(self.main_frame, text="属性")
        self.prop_panel.pack(side=tk.RIGHT, fill=tk.Y, padx=5, pady=5)

        # 在现有颜色按钮下方添加填充色按钮
        ttk.Label(self.prop_panel, text="填充色:").pack()
        self.fill_btn = ttk.Button(self.prop_panel, text="选择填充色",
                                   command=self.choose_fill_color)
        self.fill_btn.pack(pady=5)
        self.current_fill = "blue"  # 新增类属性

        # 在填充色控件下方添加自动填充按钮
        ttk.Label(self.prop_panel, text="图形模式:").pack()
        self.auto_fill_btn = ttk.Button(
            self.prop_panel,
            text="填充图形：关",
            command=self.toggle_auto_fill
        )
        self.auto_fill_btn.pack(pady=5)
        self.update_fill_button()  # 初始化按钮状态

        # 颜色选择
        self.color_btn = ttk.Button(self.prop_panel, text="颜色",
                                    command=self.choose_color)
        self.color_btn.pack(pady=5)
        self.current_color = "black"

        # 线宽设置
        ttk.Label(self.prop_panel, text="线宽:").pack()
        self.line_width = ttk.Spinbox(self.prop_panel, from_=1, to=10)
        self.line_width.set(2)
        self.line_width.pack(pady=5)

        # 变换控制
        ttk.Label(self.prop_panel, text="旋转角度:").pack()
        self.rotate_entry = ttk.Entry(self.prop_panel)
        self.rotate_entry.pack()
        ttk.Button(self.prop_panel, text="应用旋转",
                   command=self.apply_rotation).pack(pady=5)

        # 绘图画布（中间）
        self.canvas = tk.Canvas(self.main_frame, bg='white',
                                width=800, height=600)
        self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 绑定事件
        self.canvas.bind("<Button-1>", self.start_draw)
        self.canvas.bind("<B1-Motion>", self.drawing_preview)
        self.canvas.bind("<ButtonRelease-1>", self.finish_draw)
        # 输入回车,redraw
        self.canvas.bind_all("<Return>", self.handle_enter_key)
    """保存,打开功能-begin"""

    def save_project(self):
        """
        用于保存当前绘制的图像
        :return:
        """
        # 调试用打印语句（已注释），用于显示所有图形对象的类型信息
        # print(f"$$shapes is {self.shapes}$$")
        # i = 0
        # for shape in self.shapes:
        #     i += 1
        #     print(f"shape[{i}]:{shape.__class__.__name__}\n")

        # 弹出文件保存对话框，获取用户指定的保存路径
        # defaultextension设置默认文件扩展名为.vec
        # filetypes参数定义可选文件类型：矢量图形文件和所有文件
        path = asksaveasfilename(
            defaultextension=".vec",
            filetypes=[("矢量图形文件", "*.vec"), ("All Files", "*.*")]
        )

        # 如果用户取消保存（路径为空），则直接返回
        if not path:
            return

        # 构建要保存的数据字典
        # version字段表示数据格式版本（当前为1.1版）
        # shapes字段包含所有图形对象的序列化数据
        data = {
            "version": "1.1",  # 更新版本号,版本号更新说明数据结构可能有变更
            # 遍历所有图形对象，序列化每个图形为字典格式
            # 使用**解包运算符合并图形自有的to_dict()结果和补充的is_filled字段
            "shapes": [{
                ** shape.to_dict(),# 获取图形基础属性（如坐标、颜色等）
                "is_filled": shape.is_filled  # 确保填充状态被保存
            } for shape in self.shapes]
        }

        # data = {
        #     "version": "1.0",
        #     "shapes": [shape.to_dict() for shape in self.shapes]
        # }

        # 使用写入模式打开指定路径的文件
        with open(path, 'w') as f:
            # 将数据字典序列化为JSON格式写入文件
            # indent=2参数使JSON数据格式化缩进，提高可读性
            json.dump(data, f, indent=2)

    def open_project(self):
        """
        用于打开已经保存过的图像
        :return:
        """
        # 弹出文件选择对话框，让用户选择要打开的.vec文件
        # 文件类型过滤器设置为矢量图形文件(*.vec)和所有文件(*.*)
        path = askopenfilename(
            filetypes=[("矢量图形文件", "*.vec"), ("All Files", "*.*")]
        )

        # 如果用户取消选择（路径为空），则直接返回
        if not path:
            return

        # 使用上下文管理器打开选定的文件
        with open(path) as f:
            # 使用json模块加载文件内容，解析为Python字典
            data = json.load(f)

        # 清空当前项目中的图形列表（准备加载新内容）
        self.shapes.clear()

        # 遍历JSON数据中的所有图形数据
        for shape_data in data["shapes"]:
            # 根据图形类型名称从全局命名空间获取对应的类
            cls = globals()[shape_data["type"]]
            # 版本兼容处理
            if data.get("version", "1.0") < "1.1":
                # 将旧版的auto_fill字段映射到新版的is_filled字段
                shape_data["is_filled"] = shape_data.get("auto_fill", False)  # 兼容旧版数据

            # 使用图形类的from_dict方法从字典数据创建图形对象
            shape = cls.from_dict(shape_data)
            # 如果数据中指定了填充状态，强制设置图形的填充属性
            if shape_data.get("is_filled"):
                shape.is_filled = True
            # 再次从字典创建图形对象
            shape = cls.from_dict(shape_data)
            # 将图形对象添加到当前项目的图形列表中
            self.shapes.append(shape)

        # 重绘界面，显示加载的图形
        self.redraw()
    """保存,打开功能-end"""


    """自动填充函数"""
    def toggle_auto_fill(self):
        """
        切换自动填充状态
        :return:
        """

        self.auto_fill = not self.auto_fill
        # print(f"auto_fill:{self.auto_fill}")
        self.update_fill_button()

    def update_fill_button(self):
        """
        更新按钮颜色和文字
        :return:
        """
        # 如果当前的是自动填充模式
        if self.auto_fill:
            self.auto_fill_btn.config(
                text="填充图形：开",
                style="Green.TButton"  # 需要配置样式
            )
        else:
            self.auto_fill_btn.config(
                text="自动填充：关",
                style="Red.TButton"
            )

    def choose_fill_color(self):
        """
        填充色选择对话框
        功能：打开颜色选择器，允许用户选择填充颜色，并应用到当前选中的图形
        :return:
        """

        # 调用颜色选择对话框，askcolor()返回一个元组，其中[1]位置是十六进制颜色代码
        # eg:((255, 0, 0), '#ff0000')，我们取第二个元素作为颜色值
        color = colorchooser.askcolor()[1]
        if color:
            # 更新当前填充颜色：
            # - 如果用户选择了有效颜色，则使用该颜色
            # - 如果color为None，则使用默认蓝色
            self.current_fill = color if color else "blue"

            # 检查当前是否有选中的图形
            if self.selected_shape:
                # 更新选中图形的填充色
                self.selected_shape.fill = color
                self.redraw()
    """自动填充-end"""

    def clear_canvas(self):
        """
        清空画布所有内容
        :return:
        """
        self.canvas.delete("all")
        self.shapes.clear()  # 清空图形列表
        self.selected_shape = None  # 重置当前选择图形
        self.current_shape = None   # 重置当前绘制的图形
        print("画布已清空")

    def erase(self, event):
        """
        橡皮擦功能实现
        功能：擦除鼠标点击位置附近的图形元素
        :param event:
        :return:
        """
        erase_radius = 15   # 设置橡皮的半径
        to_remove = []  # 这是一个要擦除的对象的列表

        # 计算橡皮擦的有效矩形区域（用于快速筛选可能相交的图形）
        erase_left = event.x - erase_radius     # 擦除区域左边界
        erase_right = event.x + erase_radius    # 擦除区域右边界
        erase_top = event.y - erase_radius      # 擦除区域上边界
        erase_bottom = event.y + erase_radius   # 擦除区域下边界

        # 逆序遍历图形列表
        for shape in reversed(self.shapes): # 这里反向的是因为要优先擦除后面绘制的图形
            x1, y1, x2, y2 = shape.get_bounding_box()   # 获取包围盒
            # 有效区域重叠判断(矩形碰撞检测),不相交的四种情况取反
            if not (x2 < erase_left or x1 > erase_right or y2 < erase_top or y1 > erase_bottom):
                # 精确点检测,来判断鼠标点击的位置是否在图形的内部
                if shape.contains(event.x, event.y):
                    to_remove.append(shape) # 将当前图形添加到擦除列表

        # 批量删除（避免遍历时修改列表）
        for shape in to_remove:
            self.shapes.remove(shape)

        self.redraw()

    """画笔相关函数-begin"""

    def on_drag_brush_line(self, event):
        """
        处理画笔拖拽绘制过程
        实时记录画笔路径并优化绘制效果
        :param event:
        :return:
        """
        if self.current_brush:
            self.current_brush.add_point(event.x, event.y)  # 基础功能：添加当前点到画笔路径
            # 实时更新到图形列表
            if self.current_brush not in self.shapes:   # 确保当前画笔对象在图形列表中（首次添加）
                self.shapes.append(self.current_brush)
            self.redraw()
        if self.current_brush and self.drawing:
            # 动态插值优化（每5像素插入一个点）
            # 计算与上一个点的距离
            last_point = self.current_brush.points[-1]
            distance = math.hypot(event.x - last_point[0], event.y - last_point[1])
            steps = max(2, int(distance / 5))
            # 线性插值生成中间点
            for t in range(1, steps + 1):
                x = last_point[0] + (event.x - last_point[0]) * t / steps
                y = last_point[1] + (event.y - last_point[1]) * t / steps
                self.current_brush.points.append((x, y))

            # 渐进式绘制
            self._draw_live_preview()

    def _draw_live_preview(self):
        """
        事实预览
        :return:
        """
        # 判断当前是不是画笔
        if self.current_brush and len(self.current_brush.points) >= 2:
            # 仅绘制新增线段
            p1 = self.current_brush.points[-2]
            p2 = self.current_brush.points[-1]
            self.canvas.create_line(p1[0], p1[1], p2[0], p2[1],
                                    fill=self.current_color,
                                    width=self.line_width.get(),
                                    capstyle=tk.ROUND)

    def start_brush(self, event):
        """初始化画笔路径"""
        self.current_brush = Brush(
            color=self.current_color,
            line_width=int(self.line_width.get()),
            points=[(event.x, event.y)]  # 强制记录初始点
        )
        self.drawing = True  # 新增绘制状态标记
    """画笔相关函数-end"""


    def set_tool(self, tool):
        """
        这是根据当前工具来调用相应事件处理的函数
        :param tool: 当前的工具,是str
        :return:
        """
        """控制光标显示"""
        if tool == "erase_g" or tool == "erase":
            self.canvas.config(cursor="circle")   # 圆圈光标
        elif tool == "brush" or tool == "brush-dashed":
            self.canvas.config(cursor="pencil")  # 铅笔光标
        else:
            # 恢复默认光标
            self.canvas.config(cursor="arrow")  # 箭头重置

        if tool == "clear":
            self.clear_canvas()  # 直接执行清屏
            return  # 不切换当前工具
        if tool != "text":
            self.enter_TextBox = False  # 切换工具时强制关闭文本输入状态
        if tool != "FlowLine":
            # 在不是流线工具的时候清除流线所用的暂存成员变量,防止在下一次绘制流线时候造成干扰
            self.flow_line_pos = False
            self.temp_start = None

        self.current_tool = tool

        # 解除所有事件绑定
        self.canvas.unbind("<Button-1>")
        self.canvas.unbind("<B1-Motion>")
        self.canvas.unbind("<ButtonRelease-1>")

        # if tool == "brush-dashed":
        #     self.canvas.bind("<B1-Motion>", self.on_drag_dashed_line)
        #     self.canvas.config(cursor="pencil")  # 铅笔光标

        if tool in ("triangle", "star", "pentagon", "diamond", "parallelogram", "regular_hexagon","line_hexagon", "upper_cut_hexagon", "lower_cut_hexagon","generate_track"):
            # print("点击了多边形工具按钮")
            self.current_tool = "polygon"
            self.now_drawing_polygon_type = tool  # 新增多边形类型标记,这个用于在后面的start_draw()中选择对应功能
            # print(f"选择的是:{self.polygon_type}")
            # 绘图工具事件链
            self.canvas.bind("<Button-1>", self.start_draw)
            self.canvas.bind("<B1-Motion>", self.drawing_preview)
            self.canvas.bind("<ButtonRelease-1>", self.finish_draw)
        elif tool == "brush":
            self.canvas.bind("<Button-1>", self.start_brush)  # 新增初始点击绑定
            self.canvas.bind("<B1-Motion>", self.on_drag_brush_line)
            self.canvas.config(cursor="pencil")
        elif tool == "text":
            self.canvas.bind("<Button-1>", self.create_textbox)
            self.canvas.config(cursor="xterm")
            self.enter_TextBox = True # 标志着正在输入文本,仅仅在选择文本工具时激活
        elif tool == "erase":
            # 绑定橡皮擦专用事件链
            self.canvas.bind("<B1-Motion>", self.erase)
            self.canvas.bind("<Button-1>", self.erase)
            self.canvas.config(cursor="circle")  # 圆形光标
        elif tool == "select":
            # 选择工具专用事件链
            self.canvas.bind("<Button-1>", self.select_shape)
            self.canvas.bind("<B1-Motion>", self.move_shape)
            self.canvas.bind("<ButtonRelease-1>", self.finalize_move)
        elif tool == "StartOrEnd":
            self.now_drawing_polygon_type = "generate_track"    #因为开始结束符号用到了跑道形状,需要在后面使用其对应的逻辑
            self.enter_TextBox = True  # 标志着正在输入文本,仅在选择文本工具时激活
            self.canvas.bind("<Button-1>", self.start_draw)
            self.canvas.bind("<B1-Motion>", self.drawing_preview)
            self.canvas.bind("<ButtonRelease-1>", self.finish_draw)
        elif tool == "Decision":
            self.now_drawing_polygon_type = "diamond"   # 选择符号使用了菱形
            self.enter_TextBox = True  # 标志着正在输入文本,仅在选择文本工具时激活
            self.canvas.bind("<Button-1>", self.start_draw)
            self.canvas.bind("<B1-Motion>", self.drawing_preview)
            self.canvas.bind("<ButtonRelease-1>", self.finish_draw)
        elif tool == "OutputOrInput":
            self.now_drawing_polygon_type = "parallelogram" # IO符号使用了平行四边形
            self.enter_TextBox = True  # 标志着正在输入文本,仅在选择文本工具时激活
            self.canvas.bind("<Button-1>", self.start_draw)
            self.canvas.bind("<B1-Motion>", self.drawing_preview)
            self.canvas.bind("<ButtonRelease-1>", self.finish_draw)
        elif tool == "FlowLine":
            self.canvas.bind("<Button-1>", self.create_flow_line)
        elif tool == "Process":
            # self.now_drawing_polygon_type = "parallelogram"
            self.enter_TextBox = True  # 标志着正在输入文本,仅在选择文本工具时激活
            self.canvas.bind("<Button-1>", self.start_draw)
            self.canvas.bind("<B1-Motion>", self.drawing_preview)
            self.canvas.bind("<ButtonRelease-1>", self.finish_draw)
        else:
            # 绘图工具事件链
            self.canvas.bind("<Button-1>", self.start_draw)
            self.canvas.bind("<B1-Motion>", self.drawing_preview)
            self.canvas.bind("<ButtonRelease-1>", self.finish_draw)
    """流线相关函数-begin"""

    def create_flow_line(self, event):
        # print(f"当前工具是:{self.current_tool}")
        # 使用flow_line_pos来标志当前点击的点是起始点还是终止点
        if not self.flow_line_pos:
            self.temp_start = (event.x,event.y) # 使用temp_start变量存储点击的点
            # print(f"create a temp_start:{self.temp_start}")
            self.flow_line_pos = not self.flow_line_pos
        else:
            # 没有流线,先创建一个流线
            new_flow_line = FlowLine(
                start_x=self.temp_start[0], start_y=self.temp_start[1],
                end_x=event.x, end_y=event.y,  # 保持初始坐标一致
                color=self.current_color,
                line_width=int(self.line_width.get()),
                canvas_objects=self.shapes,
                is_file_mode=False,
                bend_points=[]
            )
            # print("create a FlowLine!")
            self.shapes.append(new_flow_line)  # 将流线加入shapes
            self.current_shape = new_flow_line
            self.flow_line_pos = not self.flow_line_pos
            self.current_shape.draw(canvas=self.canvas)
    """流线相关函数-end"""


    def create_textbox(self, event):
        """
        创建文本框并进入编辑模式
        :param event:
        :return:
        """
        new_text = TextBox(event.x, event.y,
                           color=self.current_color,
                           font_size=12,
                           line_width=int(self.line_width.get()))
        new_text.start_edit()
        self.shapes.append(new_text)
        self.enter_TextBox = True  # 新增状态确认
        self.redraw()

    def choose_color(self):
        """颜色选择对话框"""
        color = colorchooser.askcolor()[1]
        if color:
            self.current_color = color

    """选择-移动部分-begin"""
    def select_shape(self, event):
        """
        图形选择功能实现
        功能：处理鼠标点击选择图形对象，支持单选/多选、控制点操作和特殊图形处理
        :param event:
        """

        # ===== 第一阶段：非Ctrl键下的选择状态重置 =====
        if not (event.state & 0x0004):
            for shape in self.shapes:
                shape.selected = False

        # ===== 第二阶段：直线端点优先检测 =====
        line_handled = False
        for shape in reversed(self.shapes):# 逆序遍历（优先处理顶层图形）
            if isinstance(shape, Line):
                # 获取变换后的起点和终点坐标
                start = shape._apply_transform(shape.start)
                end = shape._apply_transform(shape.end)

                handle_size = max(12, shape.line_width * 2)  # 动态控制点大小

                # 起点检测（欧氏距离计算）
                if math.hypot(event.x - start[0], event.y - start[1]) < handle_size:
                    shape.selected = True
                    self.dragging_handle = 'start_handle'# 设置拖拽类型
                    line_handled = True
                    break

                # 终点检测（误差半径内）
                elif math.hypot(event.x - end[0], event.y - end[1]) < handle_size:
                    shape.selected = True
                    self.dragging_handle = 'end_handle'
                    line_handled = True
                    break

        # ===== 第三阶段：其他图形检测（当未处理直线时） =====
        if not line_handled:
            # 查找首个被点击的有效图形
            for shape in reversed(self.shapes):
                # 特殊处理1：文本框选择
                if isinstance(shape, TextBox) and shape.contains(event.x, event.y):
                    shape.selected = True
                    self.selected_shape = shape
                    self.dragging_handle = 'move'
                    self.last_pos = (event.x, event.y)
                    break  # 选中后立即退出循环
                # 特殊处理2：多边形选择（使用Canvas重叠检测）
                if isinstance(shape, Polygon):
                    # 通过Canvas内置方法检测多边形点击
                    overlapping = self.canvas.find_overlapping(event.x - 2, event.y - 2,
                                                               event.x + 2, event.y + 2)
                    if any(self.canvas.gettags(item) == ('polygon',) for item in overlapping):
                        shape.selected = True
                        self._draw_polygon_handles(shape)  # 绘制控制点
                # 通用图形选择逻辑
                if shape.contains(event.x, event.y):  # 需实现contains方法
                    # 多选模式切换（Ctrl键逻辑）
                    if event.state & 0x0004:
                        shape.selected = not shape.selected
                    else:
                        shape.selected = True

                    self.selected_shape = shape
                    self.dragging_handle = 'move'

                    self.last_pos = (event.x, event.y)
                    break

        # ===== 第四阶段：通用控制点检测 =====
        if self.selected_shape and not line_handled:
            bbox = self.selected_shape.get_bounding_box()
            handle_size = 8
            # 右下角缩放点检测
            if abs(event.x - bbox[2]) < handle_size and abs(event.y - bbox[3]) < handle_size:
                self.dragging_handle = 'scale'

        self.redraw()

    def _move_vertex(self, event, polygon, vertex_index):
        """
        多边形顶点拖拽处理
        功能：移动多边形特定顶点并动态调整相邻边
        :param event:
        :param polygon: 要操作的多边形对象
        :param vertex_index: 要移动的顶点索引
        """
        # 更新顶点坐标为当前鼠标位置
        polygon.points[vertex_index] = (event.x, event.y)

        # 动态更新相邻边
        if vertex_index > 0:
            self._adjust_adjacent_edges(polygon, vertex_index)

        self.redraw()

    def _draw_polygon_handles(self, polygon):
        """
        绘制多边形顶点控制手柄
        功能：为多边形每个顶点创建可交互的控制点
        :param polygon: 要绘制控制点的多边形
        """
        for i, (x, y) in enumerate(polygon.points):
            # 创建可拖拽的圆形控制点
            handle = self.canvas.create_oval(
                x - 5, y - 5, x + 5, y + 5,
                fill='#FF4444', tags=("vertex", f"poly_{id(polygon)}_v{i}")# 唯一标识标签
            )
            # 绑定拖拽事件到当前顶点
            self.canvas.tag_bind(handle, "<B1-Motion>",
                                 lambda e, idx=i: self._move_vertex(e, polygon, idx))


    def move_shape(self, event):
        """
        图形移动/缩放统一处理
        功能：根据当前操作类型处理图形变换
        :param event: 鼠标事件对象
        """
        # 特殊处理：直线端点移动
        if isinstance(self.selected_shape, Line) and self.dragging_handle:
            # 直接使用屏幕坐标差值
            dx = event.x - self.last_pos[0]
            dy = event.y - self.last_pos[1]

            # 更新起点或终点
            if self.dragging_handle == 'start_handle':
                new_start = (
                    self.selected_shape.start[0] + dx,
                    self.selected_shape.start[1] + dy
                )
                self.selected_shape.start = new_start
            elif self.dragging_handle == 'end_handle':
                new_end = (
                    self.selected_shape.end[0] + dx,
                    self.selected_shape.end[1] + dy
                )
                self.selected_shape.end = new_end

            self.last_pos = (event.x, event.y)
            self.redraw()
            return  # 避免执行其他图形处理逻辑
        # 通用图形处理
        if self.selected_shape and self.dragging_handle:
            dx = event.x - self.last_pos[0]
            dy = event.y - self.last_pos[1]

            if self.dragging_handle == 'scale':# 缩放操作
                if isinstance(self.selected_shape, Circle):
                    self.selected_shape.resize(dx, 0,self.canvas)  # 圆形只需单轴缩放
                else:
                    self.selected_shape.resize(dx, dy)
            elif self.dragging_handle == 'move':# 移动操作
                self.selected_shape.move(dx, dy)  # 使用各个图形类的move方法

            self.last_pos = (event.x, event.y)  # 更新上一次的点
            self.redraw()

    """选择-移动部分-end"""


    def start_draw(self, event):
        """
        大多数图形的点击事件的绑定的函数,承担了创建当前选择的工具对应的类实例的功能
        :param event:
        :return:
        """
        print(f"in start_draw,现在使用的工具是:{self.current_tool}")
        """开始绘图"""
        # 不去执行选择工具
        if self.current_tool == "select": return

        # 初始化一下文本输入状态
        self.enter_TextBox = isinstance(self.current_shape, TextBox)  # 根据当前图形类型设置状态

        # 新增初始化代码
        self.brush_points = []  # 清空前次绘制残留
        self.brush_points.append((event.x, event.y))  # 记录初始点

        # 清除所有选择状态
        self.selected_shape = None
        for shape in self.shapes:
            shape.selected = False

        self.drawing = True
        self.start_x = event.x
        self.start_y = event.y

        # 将当前点击的点作为多边形的中心点
        center = (event.x, event.y)

        # 根据当前工具的不同来创建不同的对象
        if self.current_tool == "brush":
            self.current_brush = Brush(
                color=self.current_color,
                line_width=int(self.line_width.get()),
                points=[(event.x, event.y)]  # 初始化时包含第一个点
            )
        elif self.current_tool == "polygon":
            """
            多边形有很多的下属图形,其基本逻辑都差不多,
            都是使用多边形生成类的多边形生成函数来生成多边形的各个顶点,之后创建一个多边形类实例,
            把上面生成的顶点赋值给points
            """
            if self.now_drawing_polygon_type == "triangle":
                points = PolygonGenerator.regular_polygon(center, 3, 50)
                self.current_shape = Polygon(
                    polygon_type="triangle",
                    points=points,
                    color=self.current_color,
                    line_width=int(self.line_width.get()),
                    auto_fill=self.auto_fill,
                    fill=self.current_fill
                )
                # print(f"正三角形:{points}")
            elif self.now_drawing_polygon_type == "star":
                points = PolygonGenerator.star(center)
                self.current_shape = Polygon(
                    polygon_type="star",
                    points=points,
                    color=self.current_color,
                    line_width=int(self.line_width.get()),
                    auto_fill=self.auto_fill,
                    fill=self.current_fill
                )
            elif self.now_drawing_polygon_type == "pentagon":
                points = PolygonGenerator.regular_polygon(center, 5, 60)
                self.current_shape = Polygon(
                    polygon_type="pentagon",
                    points=points,
                    color=self.current_color,
                    line_width=int(self.line_width.get()),
                    auto_fill=self.auto_fill,
                    fill=self.current_fill
                )
            elif self.now_drawing_polygon_type == "diamond":
                points = PolygonGenerator.diamond(center)
                self.current_shape = Polygon(
                    polygon_type="diamond",
                    points=points,
                    color=self.current_color,
                    line_width=int(self.line_width.get()),
                    auto_fill=self.auto_fill,
                    fill=self.current_fill
                )
            elif self.now_drawing_polygon_type == "parallelogram":
                points = PolygonGenerator.parallelogram(center)
                self.current_shape = Polygon(
                    polygon_type="parallelogram",
                    points=points,
                    color=self.current_color,
                    line_width=int(self.line_width.get()),
                    auto_fill=self.auto_fill,
                    fill=self.current_fill
                )
            #regular_hexagon
            elif self.now_drawing_polygon_type == "regular_hexagon":
                points = PolygonGenerator.regular_hexagon(center)
                self.current_shape = Polygon(
                    polygon_type="regular_hexagon",
                    points=points,
                    color=self.current_color,
                    line_width=int(self.line_width.get()),
                    auto_fill=self.auto_fill,
                    fill=self.current_fill
                )
            elif self.now_drawing_polygon_type == "line_hexagon":
                points = PolygonGenerator.line_hexagon(center)
                self.current_shape = Polygon(
                    polygon_type="line_hexagon",
                    points=points,
                    color=self.current_color,
                    line_width=int(self.line_width.get()),
                    auto_fill=self.auto_fill,
                    fill=self.current_fill
                )
            elif self.now_drawing_polygon_type == "upper_cut_hexagon":
                points = PolygonGenerator.upper_cut_hexagon(center)
                self.current_shape = Polygon(
                    polygon_type="upper_cut_hexagon",
                    points=points,
                    color=self.current_color,
                    line_width=int(self.line_width.get()),
                    auto_fill=self.auto_fill,
                    fill=self.current_fill
                )
            elif self.now_drawing_polygon_type == "lower_cut_hexagon":
                points = PolygonGenerator.lower_cut_hexagon(center)
                self.current_shape = Polygon(
                    polygon_type="lower_cut_hexagon",
                    points=points,
                    color=self.current_color,
                    line_width=int(self.line_width.get()),
                    auto_fill=self.auto_fill,
                    fill=self.current_fill
                )
            elif self.now_drawing_polygon_type == "generate_track":
                points = PolygonGenerator.generate_track(center)
                self.current_shape = Polygon(
                    polygon_type="generate_track",
                    points=points,
                    color=self.current_color,
                    line_width=int(self.line_width.get()),
                    auto_fill=self.auto_fill,
                    fill=self.current_fill
                )
            """下面是流程图使用的符号图形,这些图形大多都是一个多边形中间有个文本框"""
        elif self.current_tool == "StartOrEnd":
            points = PolygonGenerator.generate_track(center)
            start_or_end = StartOrEnd(
                x=event.x,
                y=event.y,
                font_size=12,
                polygon_type="generate_track",
                points=points,
                color=self.current_color,
                line_width=int(self.line_width.get()),
                auto_fill=self.auto_fill,
                fill=self.current_fill
            )
            start_or_end.start_edit()
            self.current_shape = start_or_end
            self.enter_TextBox = True
            self.redraw()
        elif self.current_tool == "Decision":
            points = PolygonGenerator.diamond(center)
            print("Decision")
            decision = Decision(
                x=event.x,
                y=event.y,
                font_size=12,
                polygon_type="Diamond",
                points=points,
                color=self.current_color,
                line_width=int(self.line_width.get()),
                auto_fill=self.auto_fill,
                fill=self.current_fill
            )
            decision.start_edit()
            self.current_shape = decision
            self.enter_TextBox = True
            self.redraw()
        elif self.current_tool == "OutputOrInput":
            points = PolygonGenerator.parallelogram(center)
            print("OutputOrInput")
            output_or_input = OutputOrInput(
                x=event.x,
                y=event.y,
                font_size=12,
                polygon_type="parallelogram",
                points=points,
                color=self.current_color,
                line_width=int(self.line_width.get()),
                auto_fill=self.auto_fill,
                fill=self.current_fill
            )
            output_or_input.start_edit()
            self.current_shape = output_or_input
            self.enter_TextBox = True
            self.redraw()
        elif self.current_tool == "Process":
            # 这里将左上角和右下角点都先初始化为一个点,后续在preview中会更新
            points = [(event.x, event.y), (event.x, event.y)]
            process = Process(
                # 这个标记了当前图形的中心点,因为矩形是使用左上,右下点来绘制的,这和文本框位置无法正确的对应
                create_pos=(event.x, event.y),
                x=event.x,
                y=event.y,
                font_size=12,
                points=points,
                color=self.current_color,
                line_width=int(self.line_width.get()),
                auto_fill=self.auto_fill,
                fill=self.current_fill
            )
            process.start_edit()
            self.current_shape = process
            self.enter_TextBox = True
            self.redraw()

            # print(f"多边形参数:{self.current_shape.to_dict()}")
        elif self.current_tool == "line":
            self.current_shape = Line(
                start_x=event.x, start_y=event.y,
                end_x=event.x, end_y=event.y,  # 保持初始坐标一致
                color=self.current_color,
                line_width=int(self.line_width.get())
            )

        elif self.current_tool == "arrow":
            self.current_shape = Arrow(
                start_x=event.x, start_y=event.y,
                end_x=event.x, end_y=event.y,  # 保持初始坐标一致
                color=self.current_color,
                line_width=int(self.line_width.get())
            )
        elif self.current_tool == "circle":

            # print(f"自动填充--{self.auto_fill}")
            self.current_shape = Circle(
                center_x=event.x,
                center_y=event.y,
                radius=0,  # 初始半径为0
                color=self.current_color,
                fill=self.current_fill,  # 新增填充色
                auto_fill=self.auto_fill,
                line_width=int(self.line_width.get())
            )

        elif self.current_tool == "rectangle":
            # print(f"In demo :auto_fill:{self.auto_fill}")
            self.current_shape = Rectangle(
                points=[(event.x, event.y), (event.x, event.y)],
                fill=self.current_fill,
                color=self.current_color,
                auto_fill=self.auto_fill,
                line_width=int(self.line_width.get()))


        elif self.current_tool == "ellipse":
            # 设置最小初始半径
            dx = max(abs(event.x - self.start_x), 1)  # 强制≥1
            dy = max(abs(event.y - self.start_y), 1)

            self.current_shape = Ellipse(
                self.start_x, self.start_y, dx, dy,
                fill=self.current_fill,
                color=self.current_color,
                auto_fill=self.auto_fill,
                line_width=int(self.line_width.get())
            )


    def drawing_preview(self, event):
        """绘制预览"""
        if not self.drawing or not self.current_shape: return
        if isinstance(self.current_shape, Polygon) or isinstance(self.current_shape, StartOrEnd) or isinstance(self.current_shape, Decision) or isinstance(self.current_shape, OutputOrInput):
            # 1. 计算包围盒基准参数
            center_x, center_y = self.start_x, self.start_y
            dx = event.x - center_x
            dy = event.y - center_y
            current_distance = math.hypot(dx, dy)
            star_d = max(dx, dy)
            """
            下面基本都是多边形的绘制预览
            逻辑上都是根据当前数据位置来更新多边形的顶点,更新方法就是使用对应形状的生成函数
            """
            if self.now_drawing_polygon_type == "star":
                center = [center_x,center_y]
                points = PolygonGenerator.star(center,outer_radius=int(star_d),inner_radius=int((star_d)/2))
                self.current_shape.points = points
            elif self.now_drawing_polygon_type =="triangle" :
                points = PolygonGenerator.regular_polygon(center=(center_x,center_y),sides=3,radius=star_d)
                self.current_shape.points = points
            elif  self.now_drawing_polygon_type =="regular_hexagon":
                points = PolygonGenerator.regular_polygon(center=(center_x,center_y),sides=6,radius=star_d)
                self.current_shape.points = points
            elif  self.now_drawing_polygon_type =="pentagon":
                points = PolygonGenerator.regular_polygon(center=(center_x,center_y),sides=5,radius=star_d)
                self.current_shape.points = points
            elif self.now_drawing_polygon_type =="diamond":
                points = PolygonGenerator.diamond(center=(center_x,center_y),width=int(dx*2) + 10,height=int(dy*2) + 10)
                self.current_shape.points = points
            elif self.now_drawing_polygon_type =="parallelogram":
                points = PolygonGenerator.parallelogram(center=(center_x,center_y),base_vector=(int(star_d * 1.5 + 10),0),height=star_d,skew_angle=30)
                self.current_shape.points = points
            elif self.now_drawing_polygon_type =="regular_hexagon":
                points = PolygonGenerator.regular_hexagon(center=(center_x,center_y),side_length=star_d)
                self.current_shape.points = points
            elif self.now_drawing_polygon_type =="line_hexagon":
                points = PolygonGenerator.line_hexagon(center=(center_x,center_y),width=int(1.3*dx),height=dy,cut_size=0.21)
                self.current_shape.points = points
            elif self.now_drawing_polygon_type =="upper_cut_hexagon":
                points = PolygonGenerator.upper_cut_hexagon(center=(center_x,center_y),width=2 * dx,height=2 * dy)
                self.current_shape.points = points
            elif self.now_drawing_polygon_type =="lower_cut_hexagon":
                points = PolygonGenerator.lower_cut_hexagon(center=(center_x,center_y),width=2 * dx,height=2 * dy)
                self.current_shape.points = points
            elif self.now_drawing_polygon_type =="generate_track":
                points = PolygonGenerator.generate_track(center=(center_x,center_y),straight_length=2 * star_d,radius=star_d)
                self.current_shape.points = points
            # else:
                # # 2. 生成原始顶点（基于局部坐标系）
                # raw_points = PolygonGenerator.regular_polygon(
                #     (0, 0),  # 局部坐标系原点
                #     sides=5 if self.now_drawing_polygon_type == "pentagon" else 3,
                #     radius=100
                # )
                #
                # # 3. 计算初始包围盒半径
                # # 修正基准半径计算（使用原始顶点极差）
                # max_x = max(p[0] for p in raw_points)
                # min_x = min(p[0] for p in raw_points)
                # max_y = max(p[1] for p in raw_points)
                # min_y = min(p[1] for p in raw_points)
                # initial_radius = math.hypot(max_x - min_x, max_y - min_y) / 2.0
                #
                # # 4. 动态计算缩放比例
                # MIN_SCALE = 0.1  # 最小缩放比例阈值
                # scale_factor = max(current_distance / initial_radius, MIN_SCALE)
                # # print(f"有效缩放比例: {scale_factor:.2f} (基准半径={initial_radius:.1f}px)")
                #
                #
                #
                # # 5. 凹凸性判断与向量调整
                # scaled_points = []
                # for i, (x, y) in enumerate(raw_points):
                #     # 计算相邻边向量
                #     prev_point = raw_points[i - 1] if i > 0 else raw_points[-1]
                #     next_point = raw_points[(i + 1) % len(raw_points)]
                #
                #     # 向量叉积判断凹凸性
                #     vec1 = (x - prev_point[0], y - prev_point[1])
                #     vec2 = (next_point[0] - x, next_point[1] - y)
                #     cross_product = vec1[0] * vec2[1] - vec1[1] * vec2[0]
                #
                #     # 凹点反向缩放
                #     if cross_product < 0:
                #         adj_scale = 1 - (1 - scale_factor) * 0.5
                #     else:
                #         adj_scale = scale_factor
                #
                #     # 应用缩放变换
                #     scaled_x = center_x + x * adj_scale
                #     scaled_y = center_y + y * adj_scale
                #     scaled_points.append((scaled_x, scaled_y))
                #     # print(f"顶点{i}: 原坐标({x:.1f},{y:.1f}) -> 缩放后({scaled_x:.1f},{scaled_y:.1f})")
                #
                # # 6. 更新图形数据
                # self.current_shape.points = scaled_points
        elif isinstance(self.current_shape, Line):
            self.current_shape.end = (event.x, event.y)
            self.redraw()  # 立即触发重绘
        elif isinstance(self.current_shape, Arrow):
            self.current_shape.end = (event.x, event.y)
            self.redraw()  # 立即触发重绘
        elif isinstance(self.current_shape, Circle):
            dx = event.x - self.start_x
            dy = event.y - self.start_y
            self.current_shape.radius = math.sqrt(dx ** 2 + dy ** 2)
        elif isinstance(self.current_shape, Process):
            # 将初始化创建时的点作为中心点来生成矩形的左上角点和右下角点
            width = abs(event.x - self.current_shape.create_pos[0])     # 对x值:将当前鼠标位置和创建时的点差值作为宽
            height = abs(event.y - self.current_shape.create_pos[1])    # 对y值:将当前鼠标位置和创建时的点差值作为高
            left =int(self.current_shape.create_pos[0] - width)
            top = int(self.current_shape.create_pos[1] + height)
            right =int(self.current_shape.create_pos[0] + width)
            bottom =int(self.current_shape.create_pos[1] - height)
            self.current_shape.points = [
                (left,top),
                (right,bottom)
            ]
            # 更新TextBox的坐标值
            self.current_shape.x = self.current_shape.create_pos[0]
            self.current_shape.y = self.current_shape.create_pos[1]
        elif isinstance(self.current_shape, Rectangle) :# or isinstance(self.current_shape, Process)
            # 将当前鼠标点作为矩形的右下角点
            self.current_shape.points[1] = (event.x, event.y)
            # if isinstance(self.current_shape, Process):
            #     self.current_shape.x = int((self.current_shape.points[0][0] + self.current_shape.points[1][0]) / 2)
            #     self.current_shape.y = int((self.current_shape.points[0][1] + self.current_shape.points[1][1]) / 2)
        elif isinstance(self.current_shape, Ellipse):
            dx = abs(event.x - self.start_x)
            dy = abs(event.y - self.start_y)
            self.current_shape.major_axis = dx
            self.current_shape.minor_axis = dy

        self.redraw()  # 统一触发重绘

    def finish_draw(self, event):
        """完成绘图"""
        self.drawing = False
        self.brush_points = []  # 彻底清空轨迹点

        self.drawing = False

        if self.current_shape:
            # 统一将图形加入到shapes
            self.shapes.append(self.current_shape)
            self.current_shape = None
            self.redraw()
        elif self.current_brush:
            # 画笔以前并不是一个单独的类,所以为了不作出太多的改变,仍然使用current_brush
            self.shapes.append(self.current_brush)
            self.current_brush = None

    def apply_rotation(self):
        """应用旋转变换"""
        if not self.selected_shape: return
        try:
            angle = float(self.rotate_entry.get())
            self.selected_shape.rotate(angle)
            self.redraw()
        except ValueError:
            pass

    def redraw(self):
        """
        刷新页面函数,将画板上所有的绘制清楚,重新去绘制shapes中的对象
        :return:
        """
        try:
            # print("redrawing!")
            self.canvas.delete("all")
            # 确保绘制所有图形对象，包括Brush
            for shape in self.shapes + ([self.current_shape] if self.current_shape else []):
                shape.draw(self.canvas)
                if shape.selected:
                    # print(f"shape's self.__class__.__name__:{shape.__class__.__name__},which is selected.")
                    self._draw_selection_handles(shape)
        except Exception as e:
            print(f"重绘错误: {str(e)}")

    def _draw_selection_handles(self, shape):
        """绘制图形选择框及控制点"""
        if not shape.selected: return
        if isinstance(shape, Line):
            p0 = shape.start
            p1 = shape.end

            # 动态控制点尺寸（线宽的2倍+基础大小）
            handle_size = max(12, shape.line_width * 2 + 4)

            # 绘制渐变控制点
            self.canvas.create_oval(
                p0[0] - handle_size, p0[1] - handle_size,
                p0[0] + handle_size, p0[1] + handle_size,
                fill='#FF4444', outline='#AA0000', width=2
            )
            self.canvas.create_oval(
                p1[0] - handle_size, p1[1] - handle_size,
                p1[0] + handle_size, p1[1] + handle_size,
                fill='#44FF44', outline='#00AA00', width=2
            )
            return
        # 获取包围盒坐标
        x1, y1, x2, y2 = shape.get_bounding_box()

        # 绘制虚线框（扩展5像素）
        self.canvas.create_rectangle(x1 - 5, y1 - 5, x2 + 5, y2 + 5,
                                     outline='red', dash=(4, 2), tags="selection")

        # 绘制四个角控制点
        handle_size = 8
        corners = [(x1, y1), (x2, y1), (x2, y2), (x1, y2)]
        for cx, cy in corners:
            self.canvas.create_oval(cx - handle_size // 2, cy - handle_size // 2,
                                    cx + handle_size // 2, cy + handle_size // 2,
                                    fill='red', tags="handle")

if __name__ == "__main__":
    root = tk.Tk()
    app = GraphicsEditor(root)
    root.mainloop()